Object class
contents
java.lang.Object 클래스는 자바 클래스 계층 구조의 최상위 루트(Root) 입니다. 여러분이 만드는 모든 클래스는(extends Object를 명시하지 않더라도) 암묵적으로 이 클래스를 상속받습니다. 심지어 배열(Array)도 객체로 취급되어 이 클래스를 상속합니다.
모든 것의 부모이기 때문에, Object를 이해한다는 것은 모든 자바 인스턴스가 반드시 지켜야 할 기본 계약(Contract) 을 이해한다는 뜻입니다.
다음은 Object 클래스의 아키텍처, 메서드, 그리고 내부 메모리 구조에 대한 상세한 분석입니다.
1. 내부 구조 (JVM 레벨)
메서드를 살펴보기 전에, java.lang.Object가 메모리(Heap 영역) 상에서 실제로 어떻게 생겼는지 이해하면 도움이 됩니다. 객체를 생성(new Object())하면, JVM은 세 부분으로 구성된 메모리를 할당합니다.
- Mark Word (헤더): 객체의 런타임 데이터를 저장합니다. 해시코드, GC(가비지 컬렉션) 세대 나이(age), 동기화를 위한 락(Lock) 정보 등이 여기에 포함됩니다.
- Class Pointer (헤더): 클래스 메타데이터(Method Area)를 가리키는 포인터입니다. JVM은 이를 통해 이 객체가 어떤 "타입(Class)"인지 식별합니다.
- Instance Data: 클래스에 정의된 실제 필드/변수 값들이 저장되는 곳입니다. (순수
Object는 필드가 없으므로 이 부분이 비어 있습니다.)
2. 11개의 Public/Protected 메서드
Object 클래스는 11개의 메서드를 정의하고 있습니다. 이를 크게 식별(Identity), 동시성(Concurrency), 생명주기(Lifecycle) 세 그룹으로 나눌 수 있습니다.
그룹 A: 식별 및 설명 (주로 오버라이딩하여 사용하는 메서드)
백엔드 개발 실무에서 가장 빈번하게 다루게 될 메서드들입니다.
1. toString()
- 기본 동작: 클래스 이름,
@기호, 그리고 해시코드의 16진수 표현을 합친 문자열을 반환합니다 (예:java.lang.Object@7852e922). - 권장 사항: DTO나 Entity 같은 데이터 클래스에서는 반드시 이 메서드를 재정의(Override)하여 사람이 읽기 쉬운 형태(예: JSON 형식)로 필드 값을 출력하도록 만드세요. 로깅과 디버깅에 필수적입니다.
2. equals(Object obj)
- 기본 동작: 참조 동등성(Reference Equality) 을 비교합니다(
==). 즉, 두 변수가 메모리상에서 정확히 같은 주소를 가리킬 때만true를 반환합니다. - 계약(Contract): 두 객체의 논리적 동등성(Logical Equality)(예: 두
User객체의ID가 같으면 같은 객체로 취급)을 비교하기 위해 이 메서드를 재정의할 경우, 반드시 다음 5가지 원칙을 지켜야 합니다.- 반사성(Reflexive):
x.equals(x)는 항상 참이어야 한다. - 대칭성(Symmetric):
x.equals(y)가 참이면y.equals(x)도 참이어야 한다. - 추이성(Transitive):
x.equals(y)가 참이고y.equals(z)가 참이면,x.equals(z)도 참이어야 한다. - 일관성(Consistent): 속성이 변하지 않았다면, 여러 번 호출해도 결과는 항상 같아야 한다.
- Null 체크:
x.equals(null)은 예외를 던지지 않고 항상 거짓(false)이어야 한다.
- 반사성(Reflexive):
3. hashCode()
- 기본 동작: 보통 객체의 내부 메모리 주소를 기반으로 정수(integer) 값을 생성합니다 (구체적인 구현은 JVM마다 다를 수 있음).
- 절대 규칙:
equals()를 재정의한다면, 반드시hashCode()도 재정의해야 합니다.equals()가true라고 판단한 두 객체는 반드시 같은hashCode값을 가져야 합니다.- 이 규칙을 어기면,
HashMap이나HashSet같은 해시 기반 컬렉션에서 객체를 제대로 찾지 못하거나 저장하지 못하는 치명적인 버그가 발생합니다.
4. getClass()
- 동작: 객체의 런타임 클래스 정보(
Class<?>)를 반환합니다.final로 선언되어 있어 오버라이딩할 수 없습니다. - 용도: 리플렉션(Reflection) API의 진입점입니다. 런타임에 메서드, 필드, 애노테이션 정보를 검사할 때 사용합니다.
그룹 B: 동시성 (스레드 통신)
이 메서드들은 객체에 연관된 "모니터(Monitor, 락)"를 제어합니다. 반드시 synchronized 블록 내부에서 호출해야 하며, 그렇지 않으면 IllegalMonitorStateException이 발생합니다.
5. wait() (3가지 변형)
- 동작: 현재 스레드가 락을 반려하고, 다른 스레드가 깨워줄 때까지 대기(sleep) 상태로 들어갑니다.
- 변형:
wait(),wait(long timeout),wait(long timeout, int nanos).
6. notify()
- 동작: 이 객체의 모니터를 기다리고 있는 스레드 중 하나를 깨웁니다. 어떤 스레드가 깨어날지는 임의로 정해집니다.
7. notifyAll()
- 동작: 대기 중인 모든 스레드를 깨웁니다. "손실된 깨움(lost wake-up)" 버그를 방지하기 위해
notify()보다 더 안전한 선택으로 간주됩니다.
참고: 최신 자바(Java 5+)에서는
wait/notify를 직접 쓰기보다java.util.concurrent패키지의 고수준 동시성 도구(Locks, Semaphores 등)를 사용하는 것을 권장합니다. 하지만 그 도구들도 내부적으로는 이 메서드들을 기반으로 작동합니다.
그룹 C: 메모리 및 생명주기
8. clone()
- 동작: 객체의 복사본을 생성하여 반환합니다.
- 주의점:
- 얕은 복사(Shallow Copy) 를 수행합니다 (기본 타입 값은 복사되지만, 객체 필드는 참조값만 복사되어 원본과 복사본이 같은 하위 객체를 공유하게 됨).
Cloneable인터페이스(마커 인터페이스)를 구현하지 않으면CloneNotSupportedException을 던집니다.
- 현재 위상: 자바 커뮤니티에서는 설계상 결함이 있다고 봅니다. 대신 복사 생성자(Copy Constructor) 나 팩토리 메서드 사용을 권장합니다.
9. finalize()
- 동작: 객체가 소멸되기 직전에 가비지 컬렉터(GC)에 의해 호출되어 정리를 수행합니다.
- 현재 위상: Java 9부터 Deprecated(사용 중지 권고) 되었습니다. 절대 사용하지 마세요.
- 이유: 실행 시점을 예측할 수 없고(GC가 언제 돌지 모름), 성능 비용이 비싸며, 죽은 객체를 다시 살려낼 위험이 있습니다.
- 대안:
AutoCloseable(try-with-resources 구문)이나java.lang.ref.Cleaner를 사용하세요.
3. 요약 테이블
| 메서드 | 반환 타입 | 설명 | 오버라이딩 가능? |
|---|---|---|---|
toString() |
String |
객체의 문자열 표현. | 가능 |
equals(Object) |
boolean |
동등성 비교. | 가능 |
hashCode() |
int |
해시 테이블용 해시코드. | 가능 |
getClass() |
Class<?> |
런타임 클래스 반환. | 불가능 (Final) |
clone() |
Object |
객체 복사본 생성. | 가능 |
wait() |
void |
스레드 일시 정지, 락 반환. | 불가능 (Final) |
notify() |
void |
대기 중인 스레드 하나 깨움. | 불가능 (Final) |
notifyAll() |
void |
대기 중인 모든 스레드 깨움. | 불가능 (Final) |
finalize() |
void |
GC 수행 전 정리 (Deprecated). | 가능 |
4. 최신 자바: "Record" 클래스
Java 14(프리뷰) 및 16(정식)부터 Records가 도입되었습니다.
여러분이 다음과 같이 레코드를 생성하면:
public record User(String name, int id) {}
자바는 필드를 기반으로 올바른 toString(), equals(), hashCode() 구현체를 자동으로 생성해 줍니다. 따라서 Object 클래스 메서드를 일일이 작성해야 하는 번거로움이 사라집니다.
5. 주니어 개발자를 위한 베스트 프랙티스
- IDE 자동 생성 기능 활용:
equals와hashCode를 절대 손으로 직접 작성하지 마세요. 오타나 논리 오류가 발생하기 쉽습니다. IntelliJ나 Eclipse의 "Generate equals() and hashCode()" 기능을 사용해 규약을 완벽히 지키는 코드를 만드세요. - Lombok 활용: 실무에서는 많은 개발자가 롬복(Lombok)의
@Data또는@EqualsAndHashCode애노테이션을 사용하여 이 메서드들을 자동 처리합니다. - finalize() 금지: 이 메서드는 존재하지 않는다고 생각하세요.
- 참조(Reference) vs 값(Value):
==은 두 변수가 같은 메모리 위치를 보는지 확인하는 것이고,equals()는 두 변수가 가진 데이터가 같은지 확인하는 것임을 항상 기억하세요.
references